package com.googlecode.beanfiles.excel;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.poi.hssf.eventusermodel.HSSFEventFactory;
import org.apache.poi.hssf.eventusermodel.HSSFListener;
import org.apache.poi.hssf.eventusermodel.HSSFRequest;
import org.apache.poi.hssf.record.BOFRecord;
import org.apache.poi.hssf.record.BoolErrRecord;
import org.apache.poi.hssf.record.BoundSheetRecord;
import org.apache.poi.hssf.record.LabelSSTRecord;
import org.apache.poi.hssf.record.NumberRecord;
import org.apache.poi.hssf.record.Record;
import org.apache.poi.hssf.record.RowRecord;
import org.apache.poi.hssf.record.SSTRecord;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;

public class EventModelIterator<Collection> implements Iterator<Collection>, HSSFListener {

    public EventModelIterator(java.io.InputStream input) {
        try {
            InputStream din = new POIFSFileSystem(input).createDocumentInputStream("Workbook"); // magic Workbook string
            HSSFRequest hssfRequest = new HSSFRequest();
            hssfRequest.addListenerForAllRecords(this);
            new HSSFEventFactory().processEvents(hssfRequest, din);
        } catch (IOException e) {
            log().error(e);
        }
    }

    private List<List<Object>> lines = new ArrayList<List<Object>>();
    private int currentLine = 0;
    private SSTRecord sstrec = null;
    private int sheetIndex = -1;

    public void processRecord(Record record) {
        if (log().isDebugEnabled()) {
            debug(record);
        }

        if (sheetIndex > 0) {
            // only processing the first sheet;
            return;
        }

        switch (record.getSid()) {
            case BOFRecord.sid:
                BOFRecord bof = (BOFRecord) record;
                if (bof.getType() == BOFRecord.TYPE_WORKSHEET) {
                    sheetIndex++;
                }
                break;
            case RowRecord.sid:
                break;
            case SSTRecord.sid:
                sstrec = (SSTRecord) record;
                break;
            case LabelSSTRecord.sid:
                LabelSSTRecord lrec = (LabelSSTRecord) record;
                processColumn(lrec.getRow(), lrec.getColumn(), sstrec.getString(lrec.getSSTIndex()).toString());
                break;
            case NumberRecord.sid:
                NumberRecord nrec = (NumberRecord) record;
                processColumn(nrec.getRow(), nrec.getColumn(), nrec.getValue());
                break;
            case BoolErrRecord.sid:
                BoolErrRecord brec = (BoolErrRecord) record;
                processColumn(brec.getRow(), brec.getColumn(), brec.getBooleanValue());
                break;
            default:
            	//log().debug(record);
            	break;
        }
    }
    
    
    private int lastColumn = 0;
    private void processColumn(int row, int column, Object value) {
    	List<Object> rowValues = null;
    	if (column == 0) {
    		rowValues = new ArrayList<Object>();
    		lines.add(rowValues);
    	} else {
    		rowValues = lines.get(row);
    	}
		fillNulls(lastColumn, column, rowValues);
    	rowValues.add(value);
    	lastColumn = column;
    }
    
    private void fillNulls(int lastColumn, int column, List<Object> values) {
    	while (++lastColumn < column) {
    		values.add(null);
    	}
    }

    public boolean hasNext() {
        if (lines != null && lines.size() > currentLine) {
            return true;
        } else {
            return false;
        }
    }

    @SuppressWarnings("unchecked")
    public Collection next() {
        if (lines != null && lines.size() > currentLine) {
            return (Collection) lines.get(currentLine++);
        } else {
            return null;
        }
    }

    public void remove() {
        throw new UnsupportedOperationException();
    }

    protected static Log log() {
        return LogFactory.getLog(EventModelIterator.class);
    }

    private void debug(Record record) {
        switch (record.getSid()) {
            case BOFRecord.sid:
                BOFRecord bof = (BOFRecord) record;
                if (bof.getType() == BOFRecord.TYPE_WORKBOOK) {
                    log().debug("Encountered workbook");
                } else if (bof.getType() == BOFRecord.TYPE_WORKSHEET) {
                    log().debug("Encountered sheet reference");
                }
                break;
            case BoundSheetRecord.sid:
                BoundSheetRecord bsr = (BoundSheetRecord) record;
                log().debug("New sheet named: " + bsr.getSheetname());
                break;
            case RowRecord.sid:
                RowRecord rowrec = (RowRecord) record;
                log().debug("Row found, first column at " + rowrec.getFirstCol() + " last column at " + rowrec.getLastCol());
                break;
            case NumberRecord.sid:
                NumberRecord numrec = (NumberRecord) record;
                log().debug(
                    "Cell found with value '" + numrec.getValue() + "' at row " + numrec.getRow() + " and column '" + numrec.getColumn());
                break;
            case SSTRecord.sid:
                sstrec = (SSTRecord) record;
                for (int k = 0; k < sstrec.getNumUniqueStrings(); k++) {
                    log().debug("String table value " + k + " = " + sstrec.getString(k));
                }
                break;
            case LabelSSTRecord.sid:
                LabelSSTRecord lrec = (LabelSSTRecord) record;
                log().debug(
                    "String cell found with value '" + sstrec.getString(lrec.getSSTIndex()) + "' at row " + lrec.getRow() + " and column " +
                            lrec.getColumn());
                break;
            case BoolErrRecord.sid:
                BoolErrRecord brec = (BoolErrRecord) record;
                log().debug(
                    "Boolean cell found with value '" + brec.getBooleanValue() + "' at row " + brec.getRow() + " and column " +
                            brec.getColumn());
                break;
            default:
                if (record.isValue()) {
                    log().debug("unhandled value record: " + record.toString());
                }
        }
    }

}
